/*
 * Copyright (c) 2025 Huawei Device, Inc. Ltd. and <马弓手>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include "Wechat.h"
#include "ConnPool.h"
#include "CordovaViewController.h"
#include "MemPool.h"
#include "cJSON.h"
#include "HttpUrl.h"
#include "FileCache.h"
#include <filemanagement/file_uri/error_code.h>
#include <filemanagement/file_uri/oh_file_uri.h>
REGISTER_PLUGIN_CLASS(Wechat)

bool Wechat::execute(const string&  action, cJSON* args, CallbackContext cbc)
{
    if(action == "onArKTsResult") {
        return onArKTsResult(args);
    }
    
    if(action == "share") {
        cJSON* pJson = cJSON_GetArrayItem(args, 0);
        if(pJson != nullptr) {
            cJSON* pMessage = cJSON_GetObjectItem(pJson, "message");
            
            string strAppId = m_preferences->getString("WECHATAPPID", "");
            cJSON_AddStringToObject(pJson, "APP_ID", strAppId.c_str());
            
            if(pMessage != nullptr) {
                cJSON* pThumb = cJSON_GetObjectItem(pMessage, "thumb");
                if(pThumb != nullptr && pThumb->type == cJSON_String) {
                    string strFileUri;
                    getFilePath(pThumb->valuestring, strFileUri);
                    if(!strFileUri.empty()) {
                        cJSON* pRespThumb = cJSON_CreateString(strFileUri.c_str());
                        cJSON_ReplaceItemInObject(pMessage, "thumb", pRespThumb);
                    }
                }
                
                cJSON* pMedia = cJSON_GetObjectItem(pMessage, "media");
                if(pMedia != nullptr) {
                    cJSON* pImage = cJSON_GetObjectItem(pMedia, "image");
                    if(pImage != nullptr) {
                        string strFileUri;
                        getUri(pImage->valuestring, strFileUri);
                        if(!strFileUri.empty()) {
                            cJSON* pUri = cJSON_CreateString(strFileUri.c_str());
                            cJSON_ReplaceItemInObject(pMedia, "image", pUri);
                        }
                    }
                    
                    cJSON* pFile = cJSON_GetObjectItem(pMedia, "file");
                    if(pFile != nullptr) {
                        string strFileUri;
                        getUri(pFile->valuestring, strFileUri);
                        if(!strFileUri.empty()) {
                            cJSON* pUri = cJSON_CreateString(strFileUri.c_str());
                            cJSON_ReplaceItemInObject(pMedia, "file", pUri);
                        }
                    }
                    
                    cJSON* pVideoUrl = cJSON_GetObjectItem(pMedia, "videoUrl");
                    if(pVideoUrl != nullptr) {
                        string strFileUri;
                        getUri(pVideoUrl->valuestring, strFileUri);
                        if(!strFileUri.empty()) {
                            cJSON* pUri = cJSON_CreateString(strFileUri.c_str());
                            cJSON_ReplaceItemInObject(pMedia, "videoUrl", pUri);
                        }
                    }
                }
            }
            
            m_cbc = cbc;
            char* pPageArgs = cJSON_Print(pJson);
            
            executeArkTs("./WeChatAction/WeChatAction/WeChatAction", 0, pPageArgs, "Wechat", cbc);
            free(pPageArgs);
            return true;
        }
    }
    
    if(action == "sendAuthRequest") {
        CordovaArgs cordovaArgs(args);
        string strScope = cordovaArgs.getString(0);
        string strState = cordovaArgs.getString(1);
        if(strScope.empty()) {
            strScope = "snsapi_userinfo";
        }
        if(strState.empty()) {
            strState = "wechat";
        }
        cJSON* pJson = cJSON_CreateObject();
        string strAppId = m_preferences->getString("WECHATAPPID", "");
        cJSON_AddStringToObject(pJson, "APP_ID", strAppId.c_str());
        cJSON_AddStringToObject(pJson, "scope", strScope.c_str());
        cJSON_AddStringToObject(pJson, "state", strState.c_str());
        m_cbc = cbc;
        char* pPageArgs = cJSON_Print(pJson);
        executeArkTs("./WeChatAction/WeChatAction/WeChatAction", 1, pPageArgs, "Wechat", cbc);
        free(pPageArgs);
        cJSON_Delete(pJson);
        return true;
    }
    
    if(action == "isWXAppInstalled") {
        m_cbc = cbc;
        string strAppId = m_preferences->getString("WECHATAPPID", "");
        executeArkTs("./WeChatAction/WeChatAction/WeChatAction", 2, strAppId.c_str(), "Wechat", cbc);
    }
    
    if(action == "openMiniProgram" || action == "minProgram") {
        int nSize = cJSON_GetArraySize(args);
        if(nSize <= 0)
            return true;
            
        cJSON* pJson = cJSON_GetArrayItem(args, 0);
        string strAppId = m_preferences->getString("WECHATAPPID", "");
        cJSON_AddStringToObject(pJson, "APP_ID", strAppId.c_str());
        m_cbc = cbc;
        char* pPageArgs = cJSON_Print(pJson);
        
        executeArkTs("./WeChatAction/WeChatAction/WeChatAction", 3, pPageArgs, "Wechat", cbc);
        free(pPageArgs);
        return true;
    }
    
    if(action == "sendPaymentRequest") {
        int nSize = cJSON_GetArraySize(args);
        if(nSize <= 0)
            return true;
            
        cJSON* pJson = cJSON_GetArrayItem(args, 0);
        string strAppId = m_preferences->getString("WECHATAPPID", "");
        cJSON_AddStringToObject(pJson, "APP_ID", strAppId.c_str());
        m_cbc = cbc;
        char* pPageArgs = cJSON_Print(pJson);
        
        executeArkTs("./WeChatAction/WeChatAction/WeChatAction", 4, pPageArgs, "Wechat", cbc);
        free(pPageArgs);
        return true;
    }
    
    return true;
}
bool Wechat::onArKTsResult(cJSON* args)
{
    cJSON* json = cJSON_GetObjectItem(args, "content");
    string strContent = "";
    if(json != NULL) {
        strContent = json->valuestring;
    }
    cJSON* pResult = cJSON_GetObjectItem(args, "result");
    if(strContent == "WechatMinProgram" || strContent == "share") {
        if(pResult != NULL && pResult->type == cJSON_Array) {
            cJSON* pRet = cJSON_GetArrayItem(pResult, 0);
            if(pRet != NULL) {
                cJSON* pRetResult = cJSON_GetArrayItem(pResult, 1);
                string strRet = pRet->valuestring;
                if(strRet == "success") {
                    PluginResult pluginResult(PluginResult::OK);
                    pluginResult.setKeepCallback(true);
                    m_cbc.sendPluginResult(pluginResult);
                } else if(strRet == "failed") {
                    m_cbc.error(strRet);
                } else if(pRetResult != nullptr){
                    string strRetResult = pRetResult->valuestring;
                    cJSON* pJsonRet = cJSON_Parse(strRetResult.c_str());
                    if(pJsonRet != nullptr) {
                        m_cbc.error(pJsonRet);
                        cJSON_Delete(pJsonRet);
                    } else {
                        m_cbc.error(strRetResult);
                    }
                } 
            }
        }
    }
    
    if(strContent == "isWXAppInstalled") {
        if(pResult != NULL && pResult->type == cJSON_Array) {
            cJSON* pRet = cJSON_GetArrayItem(pResult, 0);
            if(pRet != NULL) {
                string strRet = pRet->valuestring;
                if(strRet == "true") {
                    m_cbc.success(1);
                } else {
                    m_cbc.success(0);
                }
            }
        }
    }
    
    if(strContent == "sendAuthRequest") {
        if(pResult != NULL && pResult->type == cJSON_Array) {
            cJSON* pRet = cJSON_GetArrayItem(pResult, 0);
            if(pRet != NULL) {
                cJSON* pRetResult = cJSON_GetArrayItem(pResult, 1);
                string strRet = pRet->valuestring;
                if(pRetResult != nullptr && pRetResult->type == cJSON_String) {
                    string strRetResult = pRetResult->valuestring;
                    cJSON* pJsonRet = cJSON_Parse(strRetResult.c_str());
                    if(pJsonRet != nullptr && strRet == "true") {
                        m_cbc.success(pJsonRet);
                    } else if(pJsonRet != nullptr){
                        m_cbc.error(pJsonRet);
                    } else {
                        m_cbc.error(strRetResult);
                    }
                    if(pJsonRet != nullptr) {
                        cJSON_Delete(pJsonRet);
                    }
                }
            }
        }
    }
    
    if(strContent == "sendPaymentRequest") {
        if(pResult != NULL && pResult->type == cJSON_Array) {
            cJSON* pRet = cJSON_GetArrayItem(pResult, 0);
            if(pRet != NULL) {
                cJSON* pRetResult = cJSON_GetArrayItem(pResult, 1);
                string strRet = pRet->valuestring;
                if(pRetResult != nullptr && pRetResult->type == cJSON_String) {
                    string strRetResult = pRetResult->valuestring;
                    cJSON* pJsonRet = cJSON_Parse(strRetResult.c_str());
                    if(pJsonRet != nullptr && strRet == "true") {
                        m_cbc.success(pJsonRet);
                    } else if(pJsonRet != nullptr){
                        m_cbc.error(pJsonRet);
                    } else {
                        m_cbc.error(strRetResult);
                    }
                    if(pJsonRet != nullptr) {
                        cJSON_Delete(pJsonRet);
                    }
                }
            }
        }
    }
    
    return true;
}

bool Wechat::getBase64File(const string& strSource, string& strFilePath)
{
    string strBase64Data;
    if(strSource.length() > strSource.find("base64")+6) {
        strBase64Data = strSource.substr(strSource.find("base64")+7);
    } 
    if(strBase64Data.empty()) {
        return false;
    }
    string strFileName = HttpUrl::generateMD5(strBase64Data)+FileCache::getImageType(strBase64Data);
    string strTarget = Application::g_databaseDir.substr(0, Application::g_databaseDir.find("database")-1)+"/base/"+strFileName;
    string str = FileCache::urlDecode(strBase64Data);
    int nLength = str.length();
    char* pBuf = CBase64::decode(str.c_str(), nLength);
    if(pBuf != nullptr) {
        FileCache::writeFile(strTarget, pBuf, nLength);
    }
    strFilePath = strTarget;
    return true;
}

bool Wechat::getFilePath(const string& strSource, string& strFileUri)
{
    string strThumbPath;
    string strThumb = strSource;
    if(strThumb.find("http") == 0) { //在线文件
        if(!downloadFile(strThumb, strThumbPath)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Wechat", "downloadFile(strThumb, strThumbPath) == false");
            return false;
        } 
    } else if(strThumb.find("/data/storage/el2") == 0) { //沙箱文件
        strThumbPath = strThumb;
    }else if(strThumb.find("data:") == 0){ //base64编码
        if(!getBase64File(strThumb, strThumbPath)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Wechat", "getBase64File(strThumb, strFileUri) == false");
            return false;
        }
    } else {//rawfile资源文件
         if(!getUriFromRawfile(strThumb, strThumbPath)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Wechat", "getUriFromRawfile(strThumb, strThumbPath) == false");
            return false;
         }
    }
    
    if(!strThumbPath.empty()) {
        strFileUri = strThumbPath;
    }
    return true;
}

bool Wechat::getUri(const string& strSource, string& strFileUri)
{
    string strThumbPath;
    string strThumb = strSource;
    if(strThumb.find("http") == 0) { //在线文件
        if(!downloadFile(strThumb, strThumbPath)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Wechat", "downloadFile(strThumb, strThumbPath) == false");
            return false;
        } 
    } else if(strThumb.find("/data/storage/el2") == 0) { //沙箱文件
        strThumbPath = strThumb;
    }else if(strThumb.find("data:") == 0){ //base64编码
        if(!getBase64File(strThumb, strThumbPath)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Wechat", "getBase64File(strThumb, strFileUri) == false");
            return false;
        }    
    } else {//rawfile资源文件
         if(!getUriFromRawfile(strThumb, strThumbPath)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Wechat", "downloadFile(strThumb, strThumbPath) == false");
            return false;
         }
    }
    
    if(!strThumbPath.empty()) {
        char* result = nullptr;
        FileManagement_ErrCode errCode = OH_FileUri_GetUriFromPath(strThumbPath.c_str(),strThumbPath.length(), &result);
        if(errCode != ERR_OK) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Wechat", "OH_FileUri_GetUriFromPath(strThumbPath.c_str(),strThumbPath.length(), &result) == false");
            return false;
        }
        strFileUri = result;
        free(result);
    }
    return true;
}

bool Wechat::downloadFile(const string& strSource, string& strFilePath)
{
    vector<SMemPage> vecMemBuf;
    ((CMemPool*)Application::g_memPool)->MallocPage(vecMemBuf, 1);
    if(vecMemBuf.size() < 1) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FileTransfer", "((CMemPool*)Application::g_memPool)->MallocPage(vecMemBuf, 1) == NULL");
        return NULL;
    }
    int nBufSize = ((CMemPool*)Application::g_memPool)->GetPageSize();
    char* pBuf = vecMemBuf[0].m_point;
    
    string strExt = ".jpg";
    if(strSource.find_last_of(".") != string::npos) {
        strExt = strSource.substr(strSource.find_last_of("."));
    }
    string strFileName = HttpUrl::generateMD5(strSource) + strExt;
    string strTarget = Application::g_databaseDir.substr(0, Application::g_databaseDir.find("database")-1)+"/base/"+strFileName;
    if(FileCache::IsFile(strTarget, "")) {
        strFilePath = strTarget;
        return true;
    }
    bool isSuccess = download(strSource, strTarget, pBuf, nBufSize);
    ((CMemPool*)Application::g_memPool)->FreePage(vecMemBuf);
    if(isSuccess) {
        strFilePath = strTarget;
    }
    return isSuccess;
}

bool Wechat::download(const string& strSource, const string& strTarget, char* pBuf, const int nLength)
{
    //1，解析url
    string urlPath = strSource;
    string requestAddress = "";
    urlPath = urlPath.erase(0, urlPath.find_first_not_of(" "));
    urlPath = urlPath.erase(urlPath.find_last_not_of(" ") + 1);
    if(urlPath.find("//") != string::npos) {
        requestAddress = urlPath.substr(urlPath.find("//")+2);
    }
    
    //2,解析http/https协议类型
    int nPort = 0;
    bool isHttps = false;
    if(urlPath.find("https") == 0) {
        isHttps = true;
        nPort = 443;
    } else if(urlPath.find("http") == 0) {
        isHttps = false;
        nPort = 80;
    } else {
        return false;
    }
    
    //3解析域名修改请求地址
    string strDomainAndPort;
    string strDomain;
    if(urlPath.find("//") != string::npos) {
        strDomain = urlPath.substr(urlPath.find("//")+2);
        strDomainAndPort = strDomain;
        if(strDomain.find("/") != string::npos) {
            strDomain = strDomain.substr(0, strDomain.find("/"));
            strDomainAndPort = strDomain;
        }
        if(strDomain.find(":") != string::npos) {
            string strPort = strDomain.substr(strDomain.find(":")+1);
            strDomain = strDomain.substr(0, strDomain.find(":"));
            nPort = atoi(strPort.c_str());
        }    
        requestAddress = requestAddress.substr(strDomainAndPort.length());
    }
    
    
    string strRequest;
    strRequest = "GET " + requestAddress + " HTTP/1.1\r\n"; //GET %s HTTP/1.1\r\n
    
    vector<SMemPage> vecMemBuf;
    ((CMemPool*)Application::g_memPool)->MallocPage(vecMemBuf, 1);
    if(vecMemBuf.size() < 1) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FileTransfer", "((CMemPool*)Application::g_memPool)->MallocPage(vecMemBuf, 1) == NULL");
        return false;
    }
    
    char* szBuf = vecMemBuf[0].m_point;
    if(nPort == 80 || nPort == 443)
        sprintf(szBuf, "Host:%s\r\n", strDomain.c_str());
    else
        sprintf(szBuf, "Host:%s:%d\r\n", strDomain.c_str(), nPort);
    strRequest += szBuf;
    
    sprintf(szBuf, "Connection:Keep-Alive\r\n");
    strRequest += szBuf;
    
    string strHttpUrl = isHttps?"https://"+strDomainAndPort:"http://"+strDomainAndPort;
    if(Application::g_cookieManage != NULL && !ARKWEB_MEMBER_MISSING(Application::g_cookieManage, existCookies)) {
        if(Application::g_cookieManage->existCookies(false)) {
            if(!ARKWEB_MEMBER_MISSING(Application::g_cookieManage, fetchCookieSync)) {
                char* pCookieValue = NULL;
                Application::g_cookieManage->fetchCookieSync(strHttpUrl.c_str(), false, true, &pCookieValue);
                if(pCookieValue != NULL && strlen(pCookieValue) > 0) {
                    string strCookies = pCookieValue;
                    string strCookie = "Set-Cookie:";
                    size_t pos;
                    while ((pos = strCookies.find(strCookie)) != std::string::npos) {
                        strCookies.erase(pos, strCookie.length());
                    }
                    sprintf(szBuf, "Cookie:%s\r\n", strCookies.c_str());
                    strRequest += szBuf;
                }
                if(pCookieValue != NULL) {
                    OH_ArkWeb_ReleaseString(pCookieValue);
                }    
            }
        }
    }
    
    sprintf(szBuf, "Keep-Alive:timeout=5, max=1000\r\n\r\n");
    strRequest += szBuf;
    ((CMemPool*)Application::g_memPool)->FreePage(vecMemBuf);
    
    ConnPoolManage* connPoolManage = (ConnPoolManage*)Application::g_connPoolManage;
    ConnPool* pool = connPoolManage->getConnPool(strDomain);
    if(pool == NULL) {
        return "";
    }
    
    string strFileName = strTarget;
    map<string, string>  mapRespHeaders;
    std::atomic<bool> isAbort{false};
    if(isHttps) {
        bool isClose = false;
        SSL* ssl = NULL;
        if(!pool->getSSL(&ssl, nPort)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FileTransfer", "pool->getSocket(&ssl) == false");
        }else if(!pool->sendWithTimeOut(ssl, strRequest, std::ref(isAbort))) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FileTransfer", "pool->sendWithTimeOut(ssl, strRequest) == false");
        }else if(!pool->recvHttpWithTimeOut(ssl,  "GET", strFileName, mapRespHeaders, "", pBuf, nLength, isClose, std::ref(isAbort), nullptr)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FileTransfer", "pool->recvHttpWithTimeOut(ssl, strFileName, pMaxBuffer, nBufSize, isReconnect) == false");
        }
        pool->freeSocket(ssl, isClose);
    } else {
        bool isClose = false;
        int nSocket = -1;
        if(!pool->getSocket(nSocket, nPort)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FileTransfer", "pool->getSocket(nSocket) == false");
        }else if(!pool->sendWithTimeOut(nSocket, strRequest, std::ref(isAbort))) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FileTransfer", "pool->sendWithTimeOut(nSocket, strRequest) == false");
        }else if(!pool->recvHttpWithTimeOut(nSocket,  "GET", strFileName, mapRespHeaders, "", pBuf, nLength, isClose, std::ref(isAbort), nullptr)) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "FileTransfer", "pool->recvHttpWithTimeOut(nSocket, strFileName, pMaxBuffer, nBufSize, isReconnect) == false");
        }
        pool->freeSocket(nSocket, true);
    }
    
    if(!FileCache::IsFile(strFileName, "")) {
        return false;
    }
    return true;
}

bool Wechat::getUriFromRawfile(const string& strSource, string& strFilePath)
{
    RawFile *rawfile = OH_ResourceManager_OpenRawFile(Application::g_resourceManager, strSource.c_str());
    if(rawfile == nullptr) {
        return false;
    }
    string strExt = ".jpg";
    if(strSource.find_last_of(".") != string::npos) {
        strExt = strSource.substr(strSource.find_last_of("."));
    }
    string strFileName = HttpUrl::generateMD5(strSource) + strExt;
    
    string strTarget = Application::g_databaseDir.substr(0, Application::g_databaseDir.find("database")-1)+"/base/"+strFileName;
    FILE* pFile = FileCache::openFile(strTarget, "");
    if(pFile == nullptr) {
        return false;
    }
    vector<SMemPage> vecMemPage;
    const int blockSize = ((CMemPool*)Application::g_memPool)->GetPageSize();
    long consumed = 0;
    ((CMemPool*)Application::g_memPool)->MallocPage(vecMemPage, 1);
    unsigned  char* buffer = (unsigned  char*)vecMemPage[0].m_point;
    while (true) {
        OH_ResourceManager_SeekRawFile(rawfile, consumed, 0);
        int ret = OH_ResourceManager_ReadRawFile(rawfile, buffer, blockSize);
        if (ret == 0) {
            break;
        }
        consumed += ret;
        int nAlreadyWrite = 0;
        while(true) {
            int nRetSize = FileCache::writeFile(pFile, (const char *)buffer+nAlreadyWrite, ret);
            nAlreadyWrite += nRetSize;
            ret = ret - nRetSize;
            if(ret == 0) {
                break;
            }
        }
        memset(buffer, 0, blockSize);
    }
    ((CMemPool*)Application::g_memPool)->FreePage(vecMemPage);
    FileCache::closeFile(pFile);
    OH_ResourceManager_CloseRawFile(rawfile);
    strFilePath = strTarget;
    return true;
}